OAuth was first introduced in 2007. It was created at Twitter because Twitter wanted a way to allow third-party apps to post tweets on users' behalf. Take a second to imagine designing something like that today. How would you do it? One way would just be to ask the user for their username and password. So you create an unofficial Twitter client, and present the user a login screen that says "log in with Twitter". The user does so, but instead of logging into Twitter, they're actually sending their data to you, this third-party service which logs into Twitter for them.
OAuth 于 2007 年首次推出。它是在 Twitter 上创建的,因为 Twitter 想要一种允许第三方应用程序代表用户发布推文的方法。花点时间想象一下今天设计这样的东西。你会怎么做?一种方法是询问用户的用户名和密码。因此,您创建了一个非官方的 Twitter 客户端,并向用户显示一个登录屏幕,上面写着“使用 Twitter 登录”。用户这样做了,但他们不是登录 Twitter,而是实际上将他们的数据发送给您,这是为他们登录 Twitter 的第三方服务。
This is bad for a lot of reasons. Even if you trust a third-party app, what if they don't store your password correctly and someone steals it? You should never give your password to a third-party website like this.
这很糟糕,原因有很多。即使您信任第三方应用程序,如果他们没有正确存储您的密码并且有人窃取了它怎么办?您永远不应该将密码提供给这样的第三方网站。
Another way you might be thinking is, what about API keys? Because you're hitting Twitter's API to post data for a user, and for an API, you use API keys. But API keys are general. What you need is an API key specific to a user.
您可能想到的另一种方式是,API 密钥呢?因为你正在点击 Twitter 的 API 来为用户发布数据,而对于 API,所以你使用 API 密钥 。但 API 密钥是通用的。您需要的是特定于用户的 API 密钥。
To solve these problems, OAuth was created. You'll see how it solves all these problems, but the crux of OAuth is an access token, which is sort of like an API key for a specific user. An app gets an access token, and then they can use that to take actions on the user's behalf, or access data for a user.
为了解决这些问题,创建了 OAuth。您将看到它如何解决所有这些问题,但 OAuth 的关键是访问令牌 ,它有点像特定用户的 API 密钥。应用获取访问令牌,然后可以使用该令牌代表用户执行作,或访问用户的数据。
How OAuth works OAuth 的工作原理
OAuth can be used in a lot of different ways, one of the reasons it is so hard to understand. In this post, we’re going to look at a typical OAuth flow.
OAuth 可以以多种不同的方式使用,这也是它难以理解的原因之一。在这篇文章中,我们将看看一个典型的 OAuth 流。
The example I'm going to use is YNAB. If you haven't used it, YNAB is like a paid version of Mint. You connect it to a bank account, and then it pulls all your transactions from that account, and shows them to you with very pretty charts. You can categorize your spending, and then it tells you for example, hey, you're spending too much on groceries. It helps you manage your finances. So, I want to use YNAB, and I want to connect it to Chase Bank, but I don't want to give it my Chase password. So instead, I'm going to use OAuth.
我将使用的示例是 YNAB。如果你还没有用过,YNAB 就像 Mint 的付费版本。你把它连接到一个银行账户,然后它从该账户中提取你的所有交易,并用非常漂亮的图表向你展示。你可以对你的支出进行分类,然后它会告诉你,例如,嘿,你在杂货上花太多了。它可以帮助您管理财务。所以,我想使用 YNAB,我想将它连接到大通银行,但我不想给它我的大通密码。因此,我将使用 OAuth。
Let's look at the flow first, and then let's understand what's going on. We're actually going to look at the flow twice, because I think you need to look through an OAuth flow at least two times to understand what's going on.
我们先看看流程,然后再了解一下是怎么回事。我们实际上要查看两次流 ,因为我认为您需要至少查看两次 OAuth 流才能了解发生了什么。
OAuth flow, take 1 OAuth 流,取 1
So to start, I'm at YNAB, and I want to connect Chase as a source. The OAuth flow looks like this:
首先,我在 YNAB,我想将 Chase 作为消息来源联系起来。OAuth 流如下所示:
YNAB redirects me to Chase.
YNAB 将我重定向到 Chase。At Chase, I log in with my username and password.
在大通银行,我使用我的用户名和密码登录。Chase shows me a screen saying "YNAB wants to connect to Chase. Pick what accounts you want to give YNAB access to". It'll show me a list of all my accounts. Let's say I pick just my checking account, to give YNAB read access to this account, and hit OK.
Chase 向我展示了一个屏幕,上面写着“YNAB 想要连接到 Chase。选择您想要授予 YNAB 访问权限的帐户“。它会向我显示我所有帐户的列表。假设我只选择我的支票账户,授予 YNAB 对该账户的读取访问权限,然后点击确定。From Chase, I'm redirected back to YNAB, and now, magically, YNAB is connected to Chase.
从 Chase,我被重定向回 YNAB,现在,神奇的是,YNAB 连接到了 Chase。
This is the experience from a user's perspective. But what happened there? What magic happened in the background, so that YNAB somehow has access to my data on Chase?
这是从用户角度来看的体验。但那里发生了什么?后台发生了什么魔法,以至于 YNAB 以某种方式访问了我在 Chase 上的数据?
The end goal is to end up with an access token
最终目标是最终获得访问令牌
Remember, the end goal of OAuth is for YNAB to end up with an access token, so it can access my data from Chase. Somehow, as I went through this flow, YNAB ended up with an access token. I'll spoil the surprise by telling you how it got the access token, and then I'll walk you through what happened in more detail.
请记住,OAuth 的最终目标是让 YNAB 最终获得一个访问令牌 ,这样它就可以从 Chase 访问我的数据。不知何故,当我经历这个流程时,YNAB 最终得到了一个访问令牌。我将通过告诉您它是如何获得访问令牌来破坏惊喜的, 然后我将更详细地引导您了解发生了什么。
A quick word on security
关于安全的简短介绍
How does Chase give YNAB the access token? When you were redirected from Chase back to YNAB, Chase could have just added the access token in the URL. It could have redirected you back to a URL like this:
Chase 如何向 YNAB 提供访问令牌?当您从 Chase 重定向回 YNAB 时,Chase 可能只是在 URL 中添加了访问令牌。它可能会将您重定向回如下所示的 URL:
https://www.ynab.com/redirect?access_token=123
and then YNAB would be able to get the access token.
然后 YNAB 将能够获得访问令牌。
BAD IDEA!! 坏主意!!
An access token is supposed to be secret, but URLs can end up in your browser's history or some server logs, in which case it's easy for anyone to see your access token.
访问令牌应该是秘密的,但 URL 最终可能会出现在您浏览器的历史记录或某些服务器日志中,在这种情况下,任何人都很容易看到您的访问令牌。
So Chase could technically redirect you back to YNAB with the access token in the URL, and then YNAB would have the access token. End of OAuth flow. But we don’t do it this way, because sending an access token in the URL is not secure.
因此,从技术上讲,Chase 可以使用 URL 中的访问令牌将您重定向回 YNAB,然后 YNAB 将拥有访问令牌。OAuth 流结束。但我们不这样做,因为在 URL 中发送访问令牌并不安全。
When you were redirected from Chase back to YNAB, Chase sent YNAB an authorization code in the URL.
当您从 Chase 重定向回 YNAB 时,Chase 在 URL 中向 YNAB 发送了授权码 。
An authorization code is not an access token! Chase sends YNAB an authorization code, and YNAB exchanges the authorization code for an access token. It does this by making a backend request to Chase, a backend POST request over HTTPS, which means no one can see the access token.
授权码不是访问令牌!Chase 向 YNAB 发送授权码,YNAB 将授权码交换为访问令牌 。它通过向 Chase 发出后端请求来实现这一点,这是通过 HTTPS 的后端 POST 请求,这意味着没有人可以看到访问令牌。
And then YNAB has the access token. End of OAuth flow. OAuth success.
然后 YNAB 拥有访问令牌。OAuth 流结束。OAuth 成功。
Two parts of OAuth OAuth 的两部分
Let's talk about what we just saw. At a high level, there are two parts to an OAuth flow. The first is the user consent flow, which is where you, the user, log in and pick what to give access to. This is a critical part of OAuth, because in OAuth, we always want the user to be actively involved and in control.
让我们谈谈我们刚刚看到的。在较高级别上,OAuth 流有两个部分。第一个是用户同意流程 ,您(用户)登录并选择要授予访问权限的内容。这是 OAuth 的关键部分,因为在 OAuth 中,我们始终希望用户积极参与和控制。
The other part is the authorization code flow. This is the flow where YNAB actually gets this access token.
另一部分是授权码流 。这是 YNAB 实际获取此访问令牌的流程 。
Let's talk about more details of exactly how this works. And let's also talk about some terminology, because OAuth has very specific terminology.
让我们谈谈它到底是如何工作的更多细节。我们还来谈谈一些术语,因为 OAuth 有非常具体的术语。
Instead of user, we say resource owner.
我们说的不是用户,而是资源所有者 。Instead of app, we say OAuth client or OAuth app.
我们说的不是应用程序,而是 OAuth 客户端或 OAuth 应用程序 。The server where you log in is called the authorization server. The server where you get user data from is called the resource server (This could be the same as the authorization server).
您登录的服务器称为授权服务器 。从中获取用户数据的服务器称为资源服务器 (这可能与授权服务器相同)。On the authorization server, when the user picks what's allowed, those are called scopes.
在授权服务器上,当用户选择允许的内容时,这些称为范围 。
I'll try to use that terminology, because you'll need to get familiar with it if you're going to read more OAuth documentation.
我将尝试使用该术语,因为如果您要阅读更多 OAuth 文档,则需要熟悉它。
So let’s look at this high level again, with the new terms.
因此,让我们再次使用新术语来看看这个高水平。
OAuth flow, take 2 OAuth 流,取 2
You have OAuth clients. An OAuth client wants to access data on a resource server, and the data belongs to the resource owner.
您有 OAuth 客户端。OAuth 客户端想要访问资源服务器上的数据,并且数据属于资源所有者。
To do that, the OAuth client redirects to the authorization server. The user logs in, user agrees to scopes (what this token is allowed to access), and the user gets redirected back to the OAuth client with an authorization code in the URL.
为此,OAuth 客户端将重定向到授权服务器。用户登录,用户同意范围 (允许此令牌访问的内容),然后用户将重定向回 OAuth 客户端,并在 URL 中添加授权代码。
On the back end, the OAuth client sends the authorization code and client secret (we'll talk about client secrets shortly) to the authorization server, and the authorization server responds with the access token.
在后端,OAuth 客户端将授权码和客户端密钥(我们稍后将讨论客户端密钥)发送到授权服务器,授权服务器使用访问令牌进行响应。
That's the exact same flow, but using the new terminology we just discussed. Now let's talk specifics. We've seen what this flow looks like from the user's point of view, let's look at what it looks like from the developer's point of view.
这是完全相同的流程,但使用我们刚刚讨论的新术语。现在让我们谈谈细节。我们已经从用户的角度看到了这个流程是什么样子,让我们从开发人员的角度看看它是什么样子。
Registering a new app 注册新应用
To use OAuth, you first need to register a new app. So for example, GitHub provides OAuth. If you want to create a new app for GitHub, you first register it. Different services require different types of data in the app registration, but every service will require at least
要使用 OAuth,您首先需要注册一个新应用程序。例如,GitHub 提供了 OAuth。如果要为 GitHub 创建新应用程序,请先注册它。不同的服务在应用注册中需要不同类型的数据,但每项服务至少需要
an app name, because when the user goes to GitHub, for example, GitHub needs to be able to say "Amazon Web Services is requesting read access to your repos and gists"
应用程序名称,因为例如,当用户访问 GitHub 时,GitHub 需要能够说“Amazon Web Services 正在请求对您的存储库和 gist 的读取访问权限”A redirect URI. And we'll talk about what that is shortly.
重定向 URI。我们很快就会讨论那是什么。
GitHub will respond with:
GitHub 将做出以下回应:
A client ID. This is a public ID that you'll be using to make requests
客户端 ID。这是用于发出请求的公共 IDA client secret. You'll be using this to authenticate your request.
客户端密码。您将使用它来验证您的请求。
Awesome, you have registered your OAuth application. Let's say your app is YNAB, and one of your users wants to connect to Chase. So you start a new OAuth flow... your very first one!
太棒了,您已经注册了您的 OAuth 应用程序。假设您的应用是 YNAB,并且您的一个用户想要连接到 Chase。因此,您启动了一个新的 OAuth 流......你的第一个!
Step one: You will redirect them to Chase's authorization server's OAuth endpoint, passing these parameters in the URL:
第一步:您将它们重定向到 Chase 的授权服务器的 OAuth 端点,并在 URL 中传递以下参数:
Client ID, which we just talked about.
客户端 ID,我们刚才谈到了。The redirect URI. Once the user is done on Chase, this is where Chase will redirect them back to. This will be a YNAB url, since you're the YNAB app.
重定向 URI。用户完成 Chase 作后,Chase 会将他们重定向回此处。这将是一个 YNAB 网址,因为您是 YNAB 应用程序。Response type, which is usually "code", because we usually want to get back an authorization code, not an access token, which is less secure.
响应类型,通常是“代码”,因为我们通常希望取回授权码,而不是访问令牌,后者的安全性较低。Scopes. So what scopes are we requesting? i.e. what user data do we want to access?
范围。那么我们要求什么范围呢?即我们想要访问哪些用户数据?
This is enough information for the authorization server to validate the request and show the user a message like "YNAB is requesting read access to your accounts".
这足以让授权服务器验证请求并向用户显示一条消息,例如“YNAB 正在请求对您的帐户的读取访问权限”。
How does the authorization server validate the request? Well, if the client ID isn't valid, the request is invalid right away. If the client ID is valid, the authorization server needs to check the redirect URI. Basically, since the client ID is public, anyone could go get the YNAB client ID, and create their own OAuth flow that hits Chase, but then returns the user back to, let's say, evildude.com. But that's why when you register your app, you have to tell Chase what a valid redirect URI looks like. At that point, you would tell Chase that only YNAB.com URIs are valid, thus preventing this evildude.com scenario.
授权服务器如何验证请求?好吧,如果客户端 ID 无效,则请求立即无效。如果客户端 ID 有效,则授权服务器需要检查重定向 URI。基本上,由于客户端 ID 是公开的,任何人都可以获取 YNAB 客户端 ID,并创建自己的 OAuth 流,该流命中 Chase,然后将用户返回给 evildude.com。但这就是为什么当您注册应用程序时,您必须告诉 Chase 有效的重定向 URI 是什么样子。此时,您可以告诉 Chase 只有 YNAB.com 个 URI 有效,从而防止这种情况 evildude.com。
If everything is valid, the authorization server can use the client ID to get the app name, maybe the app icon, and then show a user consent screen.
如果一切有效,授权服务器可以使用客户端 ID 获取应用名称,也许是应用图标,然后显示用户同意屏幕。
The user will click which accounts they want to give YNAB access to, and hit okay.
用户将单击他们想要授予 YNAB 访问权限的帐户,然后单击确定。
Chase will redirect them back to the redirect URI that you gave, lets say ynab.com/oauth-callback?authorization_code=xyz.
Chase 会将它们重定向回您提供的重定向 URI,比如说 ynab.com/oauth-callback?authorization_code=xyz。
Side note: you might be wondering, what is the difference between URI and URL? Because I'm kind of using both. Well, a URL is any website URL that we know and love. URI is more general. URL is a type of URI, but there are many other types of URIs.
旁注:您可能想知道,URI 和 URL 有什么区别?因为我有点两者都用。嗯,URL 是我们熟悉和喜爱的任何网站 URL。URI 更通用。URL 是 URI 的一种类型,但还有许多其他类型的 URI。
The reason I'm saying redirect URI instead of redirect URL is because mobile apps won't have a URL. They'll just have a URI, which is a protocol they have made up that might look something like
myapp://foobar
. So if you're only doing web work, whenever you read URI, you can read it as URL. And if you're doing mobile work, you can read URI and know that yes, your use case is supported too.
我说重定向 URI 而不是重定向 URL 的原因是因为移动应用程序没有 URL。他们只会有一个 URI,这是他们编造的协议,可能看起来像myapp://foobar
。因此,如果您只做 Web 工作,那么每当您阅读 URI 时,都可以将其作为 URL 读取。如果你正在进行移动工作,你可以阅读 URI 并知道是的,你的用例也受支持。
So user is redirected back to ynab.com/oauth-callback?authorization_code=xyz, and now your app has an authorization code. You send that authorization code to the Chase authorization server, along with your client secret. Why include the client secret? Because again, the authorization code is in the URL. So anyone can see it and anyone could try to exchange it for the access token. That's why we need to send the client secret, so Chase's server can say "Oh yes I remember I had generated this code for this client ID, and the client secret matches. This is a valid request."
因此,用户被重定向回 ynab.com/oauth-callback?authorization_code=xyz,现在您的应用具有授权码。您将该授权代码连同您的客户端密钥一起发送到 Chase 授权服务器。为什么要包含客户端密码?因为同样,授权码在 URL 中。因此,任何人都可以看到它,任何人都可以尝试将其交换为访问令牌。这就是为什么我们需要发送客户端密钥,以便 Chase 的服务器可以说“哦,是的,我记得我为这个客户端 ID 生成了这个代码,客户端密钥匹配。这是一个有效的请求。
And then it returns the access token. Note how in every step of the OAuth flow, they have thought through how someone could exploit the flow, and added safeguards*. That is a big reason why it's so complicated.
然后它返回访问令牌。请注意,在 OAuth 流的每一步中,他们都考虑了如何利用该流,并添加了保护措施*。这是它如此复杂的一个重要原因。
*I'm reliably informed by a friend in security that the OAuth designers learned a bunch of lessons the hard way, and that is another reason why it is so complicated: because it had to be patched repeatedly.
*一位安全朋友可靠地告诉我,OAuth 设计者从惨痛的道路上吸取了很多教训,这也是它如此复杂的另一个原因:因为它必须反复修补。
The other big reason is because we want the user to be involved. That makes it complicated because all the user stuff has to be frontend, which is insecure, because anyone can see it. And then all the secure stuff has to be on the back end.
另一个重要原因是因为我们希望用户参与进来。这使得它变得复杂,因为所有用户的东西都必须是前端的,这是不安全的,因为任何人都可以看到它。然后所有安全的东西都必须在后端。
I keep saying frontend and back-end, but in the OAuth docs, they say front-channel and back-channel instead. Let's talk about why.
我一直说前端和后端,但在 OAuth 文档中,他们说的是前通道和后通道。我们来谈谈为什么。
Front-channel and back-channel
前通道和后通道
So, OAuth doesn't use the terms frontend and back-end, it uses front-channel and back-channel. Front-channel means GET requests, where anyone can see the parameter in the URL, and back-channel means POST requests, where that data is encrypted (as part of the POST body). The reason OAuth doesn't use frontend or backend is, because you could make POST requests using JavaScript! So, theoretically, you could exchange your authorization code for an access token right on the frontend, in JavaScript, by making a POST fetch request.
因此,OAuth 不使用术语前端和后端,而是使用前通道和后通道。前通道表示 GET 请求,任何人都可以看到 URL 中的参数,后通道表示 POST 请求,其中数据被加密(作为 POST 正文的一部分)。OAuth 不使用前端或后端的原因是,您可以使用 JavaScript 发出 POST 请求!因此,从理论上讲,您可以通过发出 POST 获取请求,在前端的 JavaScript 中将授权代码交换为访问令牌。
Now, there is a big problem with this, which is you also need the client secret to make that request. And of course, once the secret is on the frontend and accessible in JavaScript, it's not secret anymore. Anyone can access it. So, instead of using the client secret, there's a different way to do it called PKCE, spelled P-K-C-E, pronounced “pixie” (seriously). It's not as secure as doing it on the backend with the client secret, but if backend is not an option for you, you can do it using PKCE. So just know that if you have an app without a back-end, you can still do OAuth.
现在,这有一个很大的问题,那就是您还需要客户端密钥来发出该请求。当然,一旦秘密在前端并在 JavaScript 中访问,它就不再是秘密了。任何人都可以访问它。因此,除了使用客户端密码之外,还有一种不同的方法,称为 PKCE, 拼写为 P-K-C-E,发音为“pixie”(认真的)。它不如在后端使用客户端密钥执行此作那么安全,但如果后端不适合您,您可以使用 PKCE 来完成。所以只要知道,如果你有一个没有后端的应用程序,你仍然可以做 OAuth。
I may cover PKCE in a future post, as it is now recommended for the standard flow as well, since it helps protect against auth code interception.
我可能会在以后的文章中介绍 PKCE,因为现在也推荐用于标准流程,因为它有助于防止身份验证代码拦截。
Same problem for mobile apps. Unless you have a mobile app that has a backend component, like a backend server somewhere, if you're putting your client secret in a mobile app, well, anyone can get that because there are tons of tools to extract strings from mobile apps. So, instead of including your client secret in your app, you should again use PKCE to get that access token.
移动应用程序也有同样的问题。除非你有一个具有后端组件的移动应用程序,比如某个地方的后端服务器,否则如果你将客户端密码放在移动应用程序中,那么任何人都可以得到它,因为有大量的工具可以从移动应用程序中提取字符串。因此,不应在应用中包含客户端密码,而应再次使用 PKCE 来获取该访问令牌。
So those are two other terms that are good to know: front-channel and back-channel.
因此,这是另外两个值得了解的术语: 前通道和后通道 。
At this point, you've seen what the OAuth flow looks like from the user's perspective, and from the developer's perspective, and you have seen the components that make it secure.
至此,您已经从用户和开发人员的角度看到了 OAuth 流的外观,并且您已经看到了使其安全的组件。
Some final thoughts 一些最后的想法
One last thing I want to mention is OAuth can look like a lot of different ways. I covered the main recommended OAuth flow above, but some people may do OAuth by passing back an access token in the redirect instead of the authorization token (doing that is called the "implicit flow"). Some people may do it using PKCE. There's even a way to do OAuth without the user consent part, but that really is not recommended.
我想提的最后一件事是,OAuth 可以看起来像很多不同的方式。我在上面介绍了主要推荐的 OAuth 流,但有些人可能会通过在重定向中传回访问令牌而不是授权令牌来执行 OAuth(这样做称为“隐式流”)。有些人可能会使用 PKCE 来做到这一点。甚至有一种方法可以在没有用户同意部分的情况下执行 OAuth,但实际上不建议这样做。
The other part of OAuth we didn't cover is that tokens expire and you need to refresh them. And that happens through a refresh flow. Also, OAuth is all about authorization, but some workflows use OAuth to log in, such as when you use a “sign-on with Google” feature. This uses OpenID Connect, or OIDC, which is a layer on top of OAuth that also returns user data instead of just an access token. I'm mentioning this here because when you look for OAuth on the web, you'll see a lot of different flows, and you may be confused as to why they're all different. And the reason is, OAuth is not straightforward like HTTP, OAuth can look a lot of different ways.
我们没有介绍的 OAuth 的另一部分是令牌会过期,您需要刷新它们。这是通过刷新流实现的。此外,OAuth 与授权有关,但某些工作流使用 OAuth 登录,例如当您使用“使用 Google 登录”功能时。这使用 OpenID Connect 或 OIDC,它是 OAuth 之上的一层,它也会返回用户数据,而不仅仅是访问令牌。我在这里提到这一点是因为当您在 Web 上查找 OAuth 时,您会看到很多不同的流程,并且您可能会对为什么它们都不同感到困惑。原因是,OAuth 不像 HTTP 那样简单,OAuth 可以看起来很多不同的方式。
Now you're good to go out and do your own OAuthing. Good luck!
现在你可以出去做自己的 OAuthing。祝你好运!